package alecava;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Vector;
import java.io.*;
/**
 * classe che legge la versione 1.1 dei tag ID3 presenti nei file mp3
 *
 * @author Alessandro Cavallaro
 */
public class CavaID3v1 extends AbstractCavaID3 {
    /**
     * array di stringhe con i generi musicali(l'ultimo elemento(148) � vuoto
     * per essere usato con una combobox e poter impostare il form all'inizio
     * quando nessun tag � caricato)
     *
     * @see #getStringGenre()
     * @see #getVectorGenre()
     * @see #getByteGenre()
     */
    private static String[] genere = { "Blues", "Classic Rock", "Country", "Dance",
    "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age",
    "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno",
    "Industrial", "Alternative", "Ska", "Death Metal", "Pranks",
    "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal",
    "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental",
    "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise",
    "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative",
    "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic",
    "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk",
    "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta",
    "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American",
    "Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes",
    "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka",
    "Retro", "Musical", "Rock & Roll", "Hard Rock", "Folk",
    "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob",
    "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde",
    "Gothic Rock", "Progressive Rock", "Psychedelic Rock",
    "Symphonic Rock", "Slow Rock", "Big Band", "Chorus",
    "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson",
    "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass",
    "Primus", "Porn Groove", "Satire", "Slow Jam", "Club", "Tango",
    "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul",
    "Freestyle", "Duet", "Punk Rock", "Drum Solo", "Acapella",
    "Euro-House", "Dance Hall", "Goa", "Drum & Bass", "Club-House",
    "Hardcore", "Terror", "Indie", "BritPop", "Negerpunk",
    "Polsk Punk", "Beat", "Christian Gangsta Rap", "Heavy Metal",
    "Black Metal", "Crossover", "Contemporary Christian",
    "Christian Rock", "Merengue", "Salsa", "Trash Metal", "Anime",
    "Jpop", "Synthpop", "" };
    
    /**
     * buffer che contiene il tag ID3v1.1
     */
    private char[] mainTag = new char[128];
    
    /**
     * se è false il tag non è stato caricato
     */
    private boolean isLoaded = false;
    
    public CavaID3v1(File f) throws IOException{
        super(f);
        this.loadTag();
    }
    /**
     * mette le stringhe nel buffer in un certo range per la memorizzazione nel
     * file mp3
     *
     * @param from
     *            int
     * @param to
     *            int
     * @param tagStr
     *            String
     */
    private void setFromTo(int from, int to, String tagStr) {
        // calcola la grandezza
        int size = to - from;
        // variabile per la lunghezza della stringa da inserire
        int len;
        // nuovo buffer della grandezza voluta
        char[] tagBuf = new char[size];
        // se cerca di memorizzare una stringa troppo lunga prende
        // come lunghezza il massimo consentito
        // sen� prende la lunghezza voluta
        len = tagStr.length() > size ? size : tagStr.length();
        // mette i char nel buffer e se len < size
        // lascia un pezzo di buffer vuoto
        for (int i = 0; i < len; i++) {
            tagBuf[i] = tagStr.charAt(i);
        }
        // copia i dati(spazi vuoti compresi)nel buffer principale
        for (int i = from; i < to; i++) {
            mainTag[i] = tagBuf[i - from];
        }
    }
    
    /**
     * funzione generica per il recupero di un certo range di caratteri dal
     * buffer
     *
     * @param from
     *            int
     * @param to
     *            int
     * @param size
     *            int
     * @return String
     */
    private String getFromTo(int from, int to, int size) {
        String ret = new String();
        // se il tag � veramente un tag...
        if (isLoaded) {
            char[] campo = new char[size];
            // prende la sezione del buffer voluta
            for (int i = from; i < to; i++) {
                campo[i - from] = mainTag[i];
            }
            // trasforma i char in string
            for (char c : campo) {
                ret += c;
            }
            // ...sen� restituisce una stringa vuota
        } else {
            ret = "";
        }
        // toglie gli spazi vuoti
        return ret.trim();
    }
    
    /**
     * prende gli ultimi due byte del tag che contengono la traccia e l'indice
     * dell'array del genere
     *
     * @return byte[]
     */
    private short[] getTrackAndGenre() {
        short tg[] = new short[2];
        if (isLoaded) {
            // traccia
            tg[0] = (short) mainTag[126];
            // genere
            tg[1] = (short) mainTag[127];
            if (tg[1] < 0){
                //in un byte con segno non ci stanno tutti i valori necessari
                //lo trasforma in unsigned
                tg[1] += 256;
            }
            // se il tag non è caricato...
        } else {
            // ...imposta all'indice con valore vuoto dell'array
            tg[1] = (short) 148;
        }
        return tg;
        
    }
    
    /**
     * per caricare il tag � necessario chiamare prima questa funzione che legge
     * i caratteri del tag e li mette in un buffer pronto per essere utilizzato
     *
     * @param f
     *            File
     * @throws IOException
     */
    private void loadTag() throws IOException {
        
        if (getFile().canRead() && getFile().getName().indexOf(".mp3") != -1) {
            byte[] buf = new byte[128];
            RandomAccessFile mp3 = new RandomAccessFile(getFile(), "r");
            // posiziona il puntatore sugli ultimi 128 bytes
            mp3.seek(getFile().length() - 128);
            // legge i dati e li mette nel buffer
            mp3.readFully(buf);
            // chiude il file
            mp3.close();
            // controlla se � veramente un tag id3
            if ((char) buf[0] == 'T' && (char) buf[1] == 'A'
                    && (char) buf[2] == 'G') {
                //imposta la variabile di controllo a true
                isLoaded = true;
                // allora copia il buffer
                for (int i = 0; i < 128; i++) {
                    // i byte vanno convertiti in char
                    mainTag[i] = (char) buf[i];
                }
            }
        }
    }
    /**
     * scrive il nuovo tag nel file
     *
     * @param f
     *            File
     * @param artista
     *            String
     * @param titolo
     *            String
     * @param album
     *            String
     * @param commento
     *            String
     * @param anno
     *            String
     * @param genere
     *            byte
     * @param traccia
     *            byte
     * @throws FileNotFoundException
     */
    private void writeTag(String artista, String titolo, String album,
            String commento, String anno, byte genere, byte traccia)
            throws IOException {
        // chiamando la funzione si imposta il flag isLoaded per decidere se
        // sovrascrivere il tag o aggiungerlo al
        // file
        
        // imposta il buffer di char con i nuovi dati
        mainTag[0] = 'T';
        mainTag[1] = 'A';
        mainTag[2] = 'G';
        setFromTo(3, 33, titolo);
        setFromTo(33, 63, artista);
        setFromTo(63, 93, album);
        setFromTo(93, 97, anno);
        setFromTo(97, 125, commento);
        mainTag[126] = (char) traccia;
        mainTag[127] = (char) genere;
        // crea il buffer di byte e ...
        byte[] buf = new byte[128];
        // ...lo riempe da quello principale
        for (int i = 0; i < 128; i++) {
            buf[i] = (byte) mainTag[i];
        }
        // apre il file in lettura e scrittura
        RandomAccessFile mp3 = new RandomAccessFile(getFile(), "rw");
        // se ha gia un tag lo sovrascrive
        if (isLoaded) {
            mp3.seek(getFile().length() - 128);
            // sen� lo aggiunge alla fine
        } else {
            mp3.seek(getFile().length());
        }
        // scrive...
        mp3.write(buf);
        // e chiude
        mp3.close();
    }
    
    public void writeTag(String artista, String titolo, String album, String commento, String anno, String genere, String traccia) throws IOException {
        if (artista == null || titolo == null || album == null || commento == null
                || anno == null || genere == null || traccia == null) throw new NullPointerException();
        
        byte tr = 0;
        try{
            tr = Byte.parseByte(traccia);
        } catch (NumberFormatException e){
            /*prova a estrarre una traccia da qualcosa del tipo 2<separatore>10*/
            tr = parseTraccia(traccia);
        }
        byte gen = 0;
        for (int i = 0; i < this.genere.length; i++){
            if (genere.toLowerCase().equals(this.genere[i].toLowerCase())){
                gen = (byte) i;
                break;
            }
            gen = (byte) 148;
        }
        this.writeTag(artista, titolo, album, commento, anno, gen, tr);
    }
    
    public boolean hasTag(){
        return this.isLoaded;
    }
    
    /*estrae il numero di traccia da espressione del tipo 1<separatore><numero tracce>*/
    private byte parseTraccia(String traccia){
        StreamTokenizer stk = new StreamTokenizer(new StringReader(traccia));
        stk.parseNumbers();
        try {
            while (stk.nextToken() != stk.TT_EOF){
                if (stk.ttype == stk.TT_NUMBER)
                    return (byte) stk.nval;
            }
        } catch (IOException ex) {
            ;
        }
        return 0;
    }
    /**
     * <b>titolo</b>: lunghezza 30 caratteri,da indice 3 a 33
     *
     * @return String
     */
    public String getTitle() {
        return getFromTo(3, 33, 30);
    }
    
    /**
     * <b>artista</b>: lunghezza 30 caratteri,da indice 33 a 63
     *
     * @return String
     */
    public String getArtist() {
        return getFromTo(33, 63, 30);
    }
    
    /**
     * <b>album</b>: lunghezza 30 caratteri,da indice 63 a 93
     *
     * @return String
     */
    public String getAlbum() {
        return getFromTo(63, 93, 30);
    }
    
    /**
     * <b> anno</b>: lunghezza 4 caratteri,da indice 93 a 97
     *
     * @return String
     */
    public String getYear() {
        return getFromTo(93, 97, 4);
    }
    
    /**
     * <b>commento</b>: lunghezza 28 caratteri,da indice 97 a 125
     *
     * @return String
     */
    public String getComment() {
        return getFromTo(97, 125, 28);
    }
    
    /**
     * <b>traccia</b>:penultimo byte del tag
     *
     * @return String
     */
    public String getTrack() {
        return String.valueOf(getTrackAndGenre()[0]);
    }
    
    /**
     * restituisce un Vector con i generi per inizializzare una combobox
     *
     * @return Vector<String>
     */
    public static Vector<String> getVectorGenre() {
        Vector<String> gen = new Vector<String>();
        // copia l'array nel vettore
        for (String x : genere) {
            gen.addElement(x);
        }
        return gen;
    }
    
    /**
     * <b>genere</b>:ultimo byte del tag,� l'indice di un array con le stringhe
     * dei generi musicali
     *
     * @return String
     */
    public String getGenre() {
        short gen = getTrackAndGenre()[1];
        if (gen >= 0 && gen < 148)
            // usa il byte come indice dell'array
            // per prendere il giusto valore
            return genere[gen];
        else return "";
    }
    
    /**
     * <b>genere</b>:ultimo byte del tag,� l'indice di un array con le stringhe
     * (restituisce il byte stesso,utile per l'utilizzo con una combobox)
     *
     * @return byte
     */
    public short getIndexGenre() {
        return getTrackAndGenre()[1];
    }
    
}